<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
  <div id="root">
    <div class="c1">
      <div title="title1">name: {{obj.name}}</div>
      <div title="title2">age: {{obj.age}}</div>
      <div title="title3">sex: {{obj.sex}}</div>
      <ul>
        <li>{{name}}</li>
        <li>{{age}}</li>
        <li>{{sex}}</li>
      </ul>
    </div>
  </div>

  <script>
    /*
    如何将响应式和vue结合到一起？？
    vue中vnode结合数据渲染到页面是在JGVue构造函数中，且构造函数中定义了_data数据，
    响应式就是做数据的响应，从JGVue构造函数的_data定义入手
    */

  function defineReactive(target, key, value, enumerable) {
    // this就是vue实例
    let that = this;
    if(typeof value === 'object' && value != null && !Array.isArray(value)) {
      reactify(value,that) ;
    }
    Object.defineProperty(target, key, {
      configurable: true,
      enumerable: !!enumerable,
      get() {
        console.log('读取o的'+key+'属性');
        return value
      },
      set(newVal) {
        console.log('设置o的'+key+'属性为：'+newVal);
        /*
        模板刷新（这里是假的，只是演示）
        怎么获取vue实例？？？？ watcher就不会有这个问题
        传入vm vue实例
        记得是更新完数据之后在重新渲染vue模板
        */
        value = newVal;
       that.mountComponent();

      }
    })
   }

   function reactify(o, vm) {
    let keys = Object.keys(o)
    for(let i=0;i<keys.length;i++) {
      let key = keys[i]; 
      let value = o[key]
     if(Array.isArray(value)) {
      value.__proto__ = array_methods; // 数组的响应式
      for(let j=0;j<value.length;j++) {
        reactify(value[j], vm); // 传入vm实例
      }
     } else {
       defineReactive.call(vm, o, key, value, true) // call，此时defineReactive就是vue实例
    }
  }
   }
  
    let ARRAY_METHOD = [
      "push",
      "pop",
      "shift", 
      "unshift", 
      "sort", 
      "splice"
    ]
    let array_methods = Object.create(Array.prototype);
    ARRAY_METHOD.forEach(method => {
      array_methods[method] = function() {
        console.log('调用的是拦截的'+method+'方法');
        /*
        将数据进行响应式化，数据为arguments
        */
        for(let i=0;i<arguments.length;i++) {
          reactify(arguments[i])
        }

        let res = Array.prototype[method].apply(this, arguments)
        return res;
      }
    })

    class VNode {
      constructor(tag,data,value,type) {
        this.tag = tag && tag.toLowerCase();
        this.data = data;
        this.value = value;
        this.type = type;
        this.children = [];
      }
      appendChild(vnode) {
        this.children.push(vnode)
      }
    }
    function getVNode(node) {
      let nodeType = node.nodeType;
      let _vnode = null;
      if(nodeType ===1) {
        let nodeName = node.nodeName;
        let attrs = node.attributes;
        let _attrObj = {};
        for(let i=0; i<attrs.length; i++) {
          _attrObj[attrs[i].nodeName] = attrs[i].nodeValue;
        }
        _vnode = new VNode(nodeName, _attrObj, undefined, nodeType)

        let childNodes = node.childNodes;
        for(let i=0; i<childNodes.length; i++) {
          _vnode.appendChild(getVNode(childNodes[i]))
        }

      } else if(nodeType ===3) {
        _vnode = new VNode(undefined, undefined, node.nodeValue,nodeType)
      }
      return _vnode
    }
    // 将vnode转为真实dom
    function parseVNode(vnode) {
      let type = vnode.type;
      let _node = null;
      if(type ===3) { // 文本
        return document.createTextNode(vnode.value); // 创建文本节点
      } else if(type ===1) {
        _node = document.createElement(vnode.tag);
        // 属性
        let data = vnode.data; // 此时data是键值对
        Object.keys(data).forEach(key => {
          let attrName = key;
          let attrValue = data[key];
          _node.setAttribute(attrName, attrValue);
        })
        // 子元素
        let children = vnode.children;
        children.forEach(subvnode => { // subvnode就是虚拟dom vnode的子元素
          _node.appendChild(parseVNode(subvnode)) // 递归转换子元素（虚拟dom）
        })
        return _node;
      }
    }

    let rkuohao = /\{\{(.+?)\}\}/g;

    function getValueByPath(obj,path) {
    let paths = path.split('.');
    let res = obj;
    let prop;
    while(prop = paths.shift()) {
      res = res[prop]
    }
    return res
   }

    function combine(vnode, data) {
      let _type = vnode.type;
      let _data = vnode.data;
      let _value = vnode.value;
      let _tag = vnode.tag;
      let _children = vnode.children;

      let _vnode = null;
      if(_type === 3) {
        _value = _value.replace(rkuohao, function(_,g) {
          return getValueByPath(data, g.trim())
        })
        _vnode = new VNode(_tag, _data, _value, _type)
      } else if(_type === 1) {
        _vnode = new VNode(_tag, _data, _value, _type);
        _children.forEach(_subvnode => _vnode.appendChild(combine(_subvnode, data)))
      }

      return _vnode;
    }

    function JGVue(options) {
      // 响应式数据的处理，响应式数据处理
      this._data = options.data;
      this.elm = document.querySelector(options.el);
      this._template = this.elm;
      this._parent = this.elm.parentNode;
      reactify(this._data, this /*将vue实例传入，折中处理*/); // reactify没有返回值，所以不能赋值的方式调用，直接调用函数
      this.mount();
    }

    JGVue.prototype.mount = function() {
      this.render = this.createRenderFn()
      this.mountComponent()
    }

    JGVue.prototype.mountComponent = function() {
      let mount = () => { 
        this.update(this.render())
      }
      mount.call(this);
    }

    JGVue.prototype.createRenderFn = function() {
      let ast = getVNode(this._template);
      return function render() {
        let _tmp = combine(ast, this._data);
        return _tmp
      }
    }
    JGVue.prototype.update = function(vnode) {
      let realDom = parseVNode(vnode)
      this._parent.replaceChild(realDom, document.querySelector('#root'))
    }


let app = new JGVue({
  el: '#root',
  data: {
    name: '张',
    age: 'dan',
    sex: '女',
    obj: {
      name: 'obj的name',
      age: 'obj的age',
      sex: 'obj的sex'
    }
  }
})

/*
修改数据的时候模板刷新，defineReactive，set时模板刷新
*/
  </script>
</body>
</html>